package net.cloudcodex.client.widgets.spinner;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.TextBox;

/**
 * A {@link ValueSpinner} is a combination of a {@link TextBox} and a
 * {@link Spinner} to allow spinning <h3>CSS Style Rules</h3>
 * <ul class='css'>
 * <li>.ccValueSpinner { primary style }</li>
 * <li>.ccValueSpinner .textBox { the textbox }</li>
 * <li>.ccValueSpinner .arrows { the spinner arrows }</li>
 * </ul>
 */
public class ValueSpinner extends DockLayoutPanel {
	/**
	 * Resources used.
	 */
	public interface ValueSpinnerBundle extends ClientBundle {
		@Source("ValueSpinner.css")
		ValueSpinnerResource css();
	}

	public interface ValueSpinnerResource extends CssResource {
		String ccValueSpinner();

		String arrows();

		String textBox();
	}

	private static ValueSpinnerBundle bundle = initValueSpinnerBundle();

	/**
	 * @return staticly initialized {@link ValueSpinnerBundle} as GWT recommends
	 */
	private static ValueSpinnerBundle initValueSpinnerBundle() {
		final ValueSpinnerBundle valueSpinnerBundle = GWT.create(ValueSpinnerBundle.class);
		valueSpinnerBundle.css().ensureInjected();
		return valueSpinnerBundle;
	}

	private final Spinner spinner;
	private final TextBox valueBox = new TextBox();

	private final SpinnerListener spinnerListener = new SpinnerListener() {
		public void onSpinning(long value) {
			valueBox.setText(formatValue(value));
		}
	};

	private KeyPressHandler keyPressHandler = new KeyPressHandler() {

		public void onKeyPress(KeyPressEvent event) {
			int index = valueBox.getCursorPos();
			String previousText = valueBox.getText();
			String newText;
			if (valueBox.getSelectionLength() > 0) {
				newText = previousText.substring(0, valueBox.getCursorPos())
						+ event.getCharCode()
						+ previousText.substring(valueBox.getCursorPos() + valueBox.getSelectionLength(),
								previousText.length());
			} else {
				newText = previousText.substring(0, index) + event.getCharCode()
						+ previousText.substring(index, previousText.length());
			}
			valueBox.cancelKey();
			try {
				long newValue = parseValue(newText);
				if (spinner.isConstrained() && (newValue > spinner.getMax() || newValue < spinner.getMin())) {
					return;
				}
				spinner.setValue(newValue, true);
			} catch (Exception e) {
				// valueBox.cancelKey();
			}
		}
	};

	/**
	 * @param value
	 *            initial value
	 */
	public ValueSpinner(long value) {
		this(value, 0, 0, 1, 99, false);
	}

	/**
	 * @param value
	 *            initial value
	 * @param min
	 *            min value
	 * @param max
	 *            max value
	 */
	public ValueSpinner(long value, int min, int max) {
		this(value, min, max, 1, 99, true);
	}

	/**
	 * @param value
	 *            initial value
	 * @param min
	 *            min value
	 * @param max
	 *            max value
	 * @param minStep
	 *            min value for stepping
	 * @param maxStep
	 *            max value for stepping
	 */
	public ValueSpinner(long value, int min, int max, int minStep, int maxStep) {
		this(value, min, max, minStep, maxStep, true);
	}

	/**
	 * @param value
	 *            initial value
	 * @param min
	 *            min value
	 * @param max
	 *            max value
	 * @param minStep
	 *            min value for stepping
	 * @param maxStep
	 *            max value for stepping
	 * @param constrained
	 *            if set to false min and max value will not have any effect
	 */
	public ValueSpinner(long value, int min, int max, int minStep, int maxStep, boolean constrained) {
		super(Unit.PX);
		setStylePrimaryName(bundle.css().ccValueSpinner());
		spinner = new Spinner(spinnerListener, value, min, max, minStep, maxStep, constrained);
		final LayoutPanel arrowsPanel = spinner.getSpinnerPanel();
		arrowsPanel.setStylePrimaryName(bundle.css().arrows());
		addEast(arrowsPanel, Spinner.getBundle().css().ccSpinnerWidth());
		valueBox.setStylePrimaryName(bundle.css().textBox());
		valueBox.addKeyPressHandler(keyPressHandler);
		add(valueBox);
	}

	/**
	 * @return the Spinner used by this widget
	 */
	public Spinner getSpinner() {
		return spinner;
	}

	/**
	 * @param listener
	 *            the listener to add
	 */
	public void addSpinnerListener(SpinnerListener listener) {
		spinner.addSpinnerListener(listener);
	}
	
	/**
	 * @return the SpinnerListener used to listen to the {@link Spinner}
	 *         events
	 */
	public SpinnerListener getSpinnerListener() {
		return spinnerListener;
	}

	/**
	 * @return the TextBox used by this widget
	 */
	public TextBox getTextBox() {
		return valueBox;
	}

	/**
	 * @return whether this widget is enabled.
	 */
	public boolean isEnabled() {
		return spinner.isEnabled();
	}

	/**
	 * Sets whether this widget is enabled.
	 * 
	 * @param enabled
	 *            true to enable the widget, false to disable it
	 */
	public void setEnabled(boolean enabled) {
		spinner.setEnabled(enabled);
		valueBox.setEnabled(enabled);
	}
	
	/**
	 * @return the spinner value
	 */
	public long getValue() {
		return spinner.getValue();
	}

	/**
	 * @param value
	 *            the value to format
	 * @return the formatted value
	 */
	protected String formatValue(long value) {
		return String.valueOf(value);
	}

	/**
	 * @param value
	 *            the value to parse
	 * @return the parsed value
	 */
	protected long parseValue(String value) {
		return Long.valueOf(value);
	}
}
